using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using System.IO.Ports;
using System.Text;
using System.Collections;

#if MINI
using SecretLabs.NETMF.Hardware.NetduinoMini;
#else
using SecretLabs.NETMF.Hardware.Netduino;
#endif

namespace codebase
{
    public class FourDVISI
    {
        #region Declarations

        private FourDSerial fsr;
        private SerialPort sr;
        private OutputPort ro;

        private const int MAX_GENIE_REPLYS = 16;

        private VISIReply[] genieReplys = new VISIReply[MAX_GENIE_REPLYS];
        private int genieReplysHead = 0;
        private int genieReplysTail = 0;

        #endregion

        #region General

        string _Port;
        public void Initialize(string Port, int Baud, Cpu.Pin pin)
        {
            ro = new OutputPort(pin, true);

            if (ro == null) ro = new OutputPort(pin, true);
            _Port = Port;

            if (sr == null) sr = new SerialPort(_Port, Baud, Parity.None, 8, StopBits.One);

            if (!sr.IsOpen) sr.Open();

            fsr = new FourDSerial(sr);
            return;
        }

        byte[] buffer = new byte[(int)FOURD.BUFFERSIZE];
        int bc = 0;

        public void listenForEvent()
        {
            int tr = sr.BytesToRead;
            if (tr > 0) sr.Read(buffer, bc, tr);
            else return;

            for (int a = bc; a < bc + tr; a++)
            {
                //Debug.Print("IN:" + buffer[a].ToString());
            }

            bc+=tr;

            for (int n = 0; n < bc; n++)
            {
                if (buffer[n] == (byte)FOURD.NAK || buffer[n] == (byte)FOURD.ACK)
                {
                    Debug.Print("[FOURD][ACK/NAK OUT OF SYNC RECEIVED]");
                    for (int i = 0; i < bc; i++)
                    {
                        buffer[i] = buffer[i + 1];
                    }
                    bc--;
                }
                else
                {
                    break;
                }
            }

            byte[] bytes = new byte[(int)FOURD.VISIANSWERSIZE];
            if (bc >= (int)FOURD.VISIANSWERSIZE)
            {
                for (int m = 0; m < (int)FOURD.VISIANSWERSIZE; m++)
                {
                    bytes[m] = buffer[m];
                }

                for (int i = (int)FOURD.VISIANSWERSIZE; i < bc; i++)
                {
                    buffer[i - (int)FOURD.VISIANSWERSIZE] = buffer[i];
                }

                bc -= (int)FOURD.VISIANSWERSIZE;
            }
            else
            {
                return;
            }

            byte checksum = 0;

            byte cmd = 0;
            byte obj = 0;
            byte index = 0;
            byte msb = 0, lsb = 0;

            checksum = bytes[0];
            cmd = bytes[0];

            checksum ^= bytes[1];
            obj = bytes[1];

            checksum ^= bytes[2];
            index = bytes[2];

            checksum ^= bytes[3];
            msb = bytes[3];

            checksum ^= bytes[4];
            lsb = bytes[4];

            if (bytes[5] == checksum)
            {
                int next = (genieReplysHead + 1) & (MAX_GENIE_REPLYS - 1);

                if (next != genieReplysTail)
                {
                    VISIReply reply;
                    reply = new VISIReply();
                    reply.Command = (VISICommands)cmd;
                    reply.Object = (VISIObjects)obj;
                    reply.Index = index;
                    reply.Data = (int)(msb << 8 | lsb);

                    genieReplys[genieReplysHead] = reply;
                    genieReplysHead = next;
                }
            }
        }

        public bool replyAvailable()
        {
            return (genieReplysHead != genieReplysTail);
        }

        public VISIReply getReply()
        {
            VISIReply tmpReply = new VISIReply();
            tmpReply = genieReplys[genieReplysTail];

            genieReplysTail = (genieReplysTail + 1) & (MAX_GENIE_REPLYS - 1);

            handleGenieEvent(tmpReply);

            return tmpReply;
        }

        #endregion

        #region Events

        //public event VISIEventHandler DipSwitch_Changed;
        //public event VISIEventHandler Knob_Changed;
        //public event VISIEventHandler RockerSwitch_Changed;
        //public event VISIEventHandler RotarySwitch_Changed;
        //public event VISIEventHandler Slider_Changed;
        //public event VISIEventHandler TrackBar_Changed;
        public event VISIEventHandler WinButton_Changed;
        //public event VISIEventHandler AngularMeter_Changed;
        //public event VISIEventHandler CoolGauge_Changed;
        //public event VISIEventHandler CustomDigits_Changed;
        public event VISIEventHandler Form_Changed;
        //public event VISIEventHandler Gauge_Changed;
        public event VISIEventHandler Keyboard_Changed;
        //public event VISIEventHandler Led_Changed;
        //public event VISIEventHandler LedDigits_Changed;
        //public event VISIEventHandler Meter_Changed;
        //public event VISIEventHandler Strings_Changed;
        //public event VISIEventHandler Thermometer_Changed;
        //public event VISIEventHandler UserLed_Changed;
        //public event VISIEventHandler Video_Changed;
        //public event VISIEventHandler Sound_Changed;
        //public event VISIEventHandler Timer_Changed;

        public event VISIEventHandler Report_Get;

        public void handleGenieEvent(VISIReply reply)
        {
            switch (reply.Command)
            {
                case VISICommands.REPORT_EVENT:
                    switch (reply.Object)
                    {
                        case VISIObjects.Form:
                            if (Form_Changed != null) Form_Changed(new object(), new VISIEventArgs(reply));
                            break;
                        case VISIObjects.WinButton:
                            if (WinButton_Changed != null) WinButton_Changed(new object(), new VISIEventArgs(reply));
                            break;
                        case VISIObjects.Keyboard:
                            if (Keyboard_Changed != null) Keyboard_Changed(new object(), new VISIEventArgs(reply));
                            break;
                        default:
                            return;
                    }
                    break;
                case VISICommands.REPORT_OBJ:
                    if (Report_Get != null) Report_Get(new object(), new VISIEventArgs(reply));
                    break;
                default:
                    return;
            }

            return;
        }

        #endregion

        #region Functions

        public int readObj(VISIObjects obj, int index)
        {
            VISIReply reply;
            byte checksum;

            while (replyAvailable()) reply = getReply();

            fsr.sendByte((byte)VISICommands.READ_OBJ); checksum = (byte)VISICommands.READ_OBJ;
            fsr.sendByte((byte)obj); checksum ^= (byte)obj;
            fsr.sendByte((byte)index); checksum ^= (byte)index;
            fsr.sendByte(checksum);
            fsr.send();

            if (replyAvailable())
            {
                reply = getReply();
                if ((reply.Command == VISICommands.REPORT_OBJ) && (reply.Object == obj) && (reply.Index == index))
                    return reply.Data;
            }

            return -1;
        }

        public void writeObj(VISIObjects obj, int index, int data)
        {
            int msb, lsb;
            byte checksum;

            lsb = (data >> 0) & 0xFF;
            msb = (data >> 8) & 0xFF;

            fsr.sendByte((byte)VISICommands.WRITE_OBJ); checksum = (byte)VISICommands.WRITE_OBJ;
            fsr.sendByte((byte)obj); checksum ^= (byte)obj;
            fsr.sendByte((byte)index); checksum ^= (byte)index;
            fsr.sendByte((byte)msb); checksum ^= (byte)msb;
            fsr.sendByte((byte)lsb); checksum ^= (byte)lsb;
            fsr.sendByte(checksum);

            fsr.sendAndGetACK();
        }

        public void writeContrast(byte value)
        {
            byte checksum;

            fsr.sendByte((byte)VISICommands.WRITE_CONTRAST); checksum = (byte)VISICommands.WRITE_CONTRAST;
            fsr.sendByte(value); checksum ^= value;
            fsr.sendByte(checksum);

            fsr.sendAndGetACK();
        }

        void _writeStrX(VISICommands code, int index, string text)
        {
            byte checksum;

            byte[] textBytes = Encoding.UTF8.GetBytes(text);
            int len = textBytes.Length;

            if (len > 255)
                return;

            fsr.sendByte((byte)code); checksum = (byte)code;
            fsr.sendByte((byte)index); checksum ^= (byte)index;
            fsr.sendByte((byte)len); checksum ^= (byte)len;

            foreach (byte bt in textBytes)
            {
                fsr.sendByte(bt);
                checksum ^= bt;
            }

            fsr.sendByte(checksum);
            fsr.sendAndGetACK();
        }

        public void writeStr(int index, string text)
        {
            _writeStrX(VISICommands.WRITE_STR, index, text);
        }

        public void writeStrU(int index, string text)
        {
            _writeStrX(VISICommands.WRITE_STRU, index, text);
        }

        #endregion
    }
}
